/*
 * Copyright 2011 Stefan Ferstl
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.code.debmavenplugin.archiver.ar.deb;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.code.debmavenplugin.archiver.ArchiveMetaData;
import com.google.code.debmavenplugin.archiver.SpecialFileArchiveMetaData;
import com.google.code.debmavenplugin.archiver.targz.TarGzArchiver;


/**
 * Archiver that creates a Debian package's control archive.
 */
public class ControlArchiveArchiver extends TarGzArchiver {

  private static final Logger LOGGER = LoggerFactory.getLogger(ControlArchiveArchiver.class);
  private static final String CONTROL_FILE = "control";
  private static final String MD5SUMS_FILE = "md5sums";
  private static final String POSTINST_FILE = "postinst";
  private static final String PRERM_FILE = "prerm";
  private static final int EXEC_FILE_MODE = 0755;

  private final File outputDirectory;
  private final File controlFilesDirectory;
  private final ArchiveMetaData dataArchiveMetaData;
  /**
   * This flag stores whether the control files directory is the same as the
   * output directory. If the directories are different, the control files need
   * to be copied before the control archive is created.
   */
  private final boolean copyFiles;

  /**
   * Constructor.
   *
   * @param dataArchiveMetaData Metadata of the <strong>data</strong> archive.
   * This data is needed to create the checksum file.
   * @param controlFilesDirectory Directory containing the static control files.
   * @param outputDirectory Directory to write the control archive to.
   */
  public ControlArchiveArchiver(ArchiveMetaData dataArchiveMetaData, File controlFilesDirectory, File outputDirectory) {
    super(new File(outputDirectory, DebPackageConstants.CONTROL_ARCHIVE),
        createControlArchiveMetaData(outputDirectory), false);
    this.dataArchiveMetaData = dataArchiveMetaData;
    this.controlFilesDirectory = controlFilesDirectory;
    this.outputDirectory = outputDirectory;
    // We do only need to copy files in case the control file directory is not the output directory.
    this.copyFiles = !controlFilesDirectory.equals(outputDirectory);
  }

  /**
   * Overwrites the original method in order to copy the control files to the
   * right location and to create the checksum file in advance.
   * @throws IOException In case something goes wrong.
   */
  @Override
  public void createArchive() throws IOException {
    LOGGER.debug("Start controlfile preparation");
    prepareControlFiles();
    LOGGER.debug("Start archive creation");
    super.createArchive();
    LOGGER.debug("Cleanup copied files");
    cleanupControlFiles();
  }

  /**
   * Creates the checksum file and copies the control files to the same
   * directory (this needs to be done since the checksum file and the other
   * control files have to be in the same directory).
   *
   * @throws IOException In case something goes wrong.
   */
  private void prepareControlFiles() throws IOException {

    // Write the checksum file
    LOGGER.debug("Write checksum file");
    ChecksumFileWriter csFileWriter = new ChecksumFileWriter(this.dataArchiveMetaData, new File(this.outputDirectory, MD5SUMS_FILE));
    csFileWriter.createChecksumFile();

    // We need to copy the control files to the output directory because
    // ArchiveMetaData needs to have all files in one directory and we
    // have to generate the md5sums file at a well-known place, which is
    // the output directory of this packer.
    copyControlFile(CONTROL_FILE);
    copyControlFile(POSTINST_FILE);
    copyControlFile(PRERM_FILE);
  }

  /**
   * Deletes the control files silently.
   */
  private void cleanupControlFiles() {
    deleteControlFile(CONTROL_FILE, false);
    deleteControlFile(POSTINST_FILE, false);
    deleteControlFile(PRERM_FILE, false);
    deleteControlFile(MD5SUMS_FILE, true);
  }

  /**
   * Creates the metadata of the control archive.
   * @param outputDirectory Directory where the files for the control archive are located.
   * @return The created metadata.
   */
  private static ArchiveMetaData createControlArchiveMetaData(File outputDirectory) {
    ArchiveMetaData controlArchiveMetaData = new SpecialFileArchiveMetaData(outputDirectory, DebPackageConstants.ROOT, DebPackageConstants.ROOT, null, null);
    controlArchiveMetaData.addSpecialFile(
        CONTROL_FILE, DebPackageConstants.ROOT, DebPackageConstants.ROOT, DebPackageConstants.FILE_MODE);
    controlArchiveMetaData.addSpecialFile(
        MD5SUMS_FILE, DebPackageConstants.ROOT, DebPackageConstants.ROOT, DebPackageConstants.FILE_MODE);
    controlArchiveMetaData.addSpecialFile(
        POSTINST_FILE, DebPackageConstants.ROOT, DebPackageConstants.ROOT, EXEC_FILE_MODE);
    controlArchiveMetaData.addSpecialFile(
        PRERM_FILE, DebPackageConstants.ROOT, DebPackageConstants.ROOT, EXEC_FILE_MODE);
    return controlArchiveMetaData;
  }

  /**
   * Copies the specified control file to the output directory.
   * @param fileName Name of the control file to copy.
   * @throws IOException In case something goes wrong.
   */
  private void copyControlFile(String fileName) throws IOException {
    File srcFile = new File(this.controlFilesDirectory, fileName);
    File destFile = new File(this.outputDirectory, fileName);
    if (this.copyFiles) {
      LOGGER.debug("Copy controlfile '{}' from '{}' to '{}'",
          new Object[] { fileName, this.controlFilesDirectory, this.outputDirectory });
      FileUtils.copyFile(srcFile, destFile);
    }
  }

  /**
   * Deletes the specified control file silently. The file is only be deleted in
   * case it was copied to the output directory of this archiver of if the force
   * flag is set to <code>true</code>.
   *
   * @param fileName The control file to be deleted.
   * @param force <code>true</code> if the file should be deleted in any case.
   */
  private void deleteControlFile(String fileName, boolean force) {
    File file = new File(this.outputDirectory, fileName);
    if (this.copyFiles || force) {
      boolean deleted = FileUtils.deleteQuietly(file);
      if (!deleted) {
        LOGGER.info("Control file '{}' could not be deleted.", fileName);
      }
    }
  }
}
